#include"definicje.h"
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netdb.h>
#include<unistd.h>
#include<unistd.h>
#include"protokol.h"
#include"adres.h"
#include"kompresja.h"
#include<openssl/rsa.h>
#include<openssl/pem.h>
#include<openssl/err.h>
#include"przeksztalcenia2.h"

/* lista serwerów */
struct adres adresy[1]=
{
   {"localhost","9999"},
};
struct adresIPv6 adresyIPv6[1]=
{
   {"localhost","10000"},
};

const unsigned char * const gg="adc0f13a0ce06b253c26b21be3ba47f6dc65238c6fdca4da2b0c89a995af87c0481c2e6adb02474114bef96d0aa229058b7ec91940c365c04a00e409dcd2469b";
const unsigned char * const pp="ea8434f51293b3c2cd231d5c4e61116836711dca76c0bc57cfed21977746712844033377961e2fe83e6ab265f4ede4b9d158a1c32534c8ba40e65936130589f5";

mpz_t G;
mpz_t P;
mpz_t klucz_oryginalny;
mpz_t klucz_aktualny;

/* bufor do przechowywania pakietów */
int8_t bufor[MAX_PAKIET];
struct naglowek *nag=(struct naglowek *)bufor;
int8_t *dane=bufor+rozmiarNaglowka;

/* bufor na potwierdzenia */
int8_t bufor2[MAX_PAKIET];
struct pakietACK *ack=(struct pakietACK *)bufor2;
int8_t *dane2=bufor2+rozmiarACK;

/* Tworzy gniazdo i podaje adres */
void stworz(int *g, struct sockaddr_storage * s)
{
   unsigned int i;
   int wynik;
   struct addrinfo wskazowki;
   struct addrinfo *wyniki, *wskaznik;
   int gniazdo;
   bool utworzono;

   memset(&wskazowki, 0, sizeof(struct addrinfo));
   //wskazowki.ai_family = AF_UNSPEC;
   //wskazowki.ai_family = AF_INET;
   wskazowki.ai_socktype = SOCK_DGRAM;
   utworzono=false;
   while(!utworzono)
   {
      for(i=0; (i<sizeof(adresy)/sizeof(struct adres)) && ! utworzono; ++i)
      {
	 wskazowki.ai_family = AF_INET;
	 wynik=getaddrinfo(adresy[i].serwer,adresy[i].port,&wskazowki,&wyniki);
	 if(wynik!=0)
	 //if(wynik==0)
	 {
	    wskazowki.ai_family = AF_INET6;
	    wynik=getaddrinfo(adresyIPv6[i].serwer,adresyIPv6[i].port,&wskazowki,&wyniki);
	    if(wynik!=0)
	    {
	       fprintf(stderr, "getaddrinfo: %s\n",gai_strerror(wynik));
	       continue;
	    }
	    fprintf(stderr,"IPv6\n");
	 }
	 for(wskaznik = wyniki; (wskaznik != NULL) && ! utworzono; wskaznik=wskaznik->ai_next)
	 {
	    /*** Tu otwierane gniazdo - trzeba zamknąć ***/
	    gniazdo=socket(
		  wskaznik->ai_family,
		  wskaznik->ai_socktype,
		  wskaznik->ai_protocol
		  );
	    if(gniazdo==-1)
	    {
	       perror("socket");
	       continue;
	    }
	    utworzono=true;
	    memcpy(s,wskaznik->ai_addr,wskaznik->ai_addrlen);
	    *g=gniazdo;
	 }
	 freeaddrinfo(wyniki);
      }
   }
}

int main(int argc, char *argv[])
{

   int gniazdo;
   uint32_t ktory=0;
   uint32_t ktoryTam=0;
   uint16_t licznik;
   size_t r;
   int wynik;
   FILE *plik;
   FILE *plik_temp;
   FILE *plik_temp1;
   struct sockaddr_storage adres;
   uint8_t klucz[64];
   unsigned int ile;

   inicjalizuj();
   mpz_init(G);
   mpz_init(P);
   mpz_set_str(G,gg,16);
   mpz_set_str(P,pp,16);
   ustawG(G);
   ustawP(P);

   mpz_init(klucz_oryginalny);
   mpz_init(klucz_aktualny);

   do
   {
      generujPierwsza(klucz_oryginalny);
   }
   while((mpz_cmp(klucz_oryginalny,G)==0) || (mpz_cmp(klucz_oryginalny,P)==0));
   mpz_export(klucz,&ile,1,sizeof(uint8_t),1,0,klucz_oryginalny);

    unsigned char* encrypted = (unsigned char*)malloc(128);
    unsigned char* decrypted = (unsigned char*)malloc(128);
    int bufSize;



    FILE *keyfile=fopen("server-public.pem","r");
    FILE *keyfile2=fopen("klient-public.pem","r");
    RSA *rsa_priv = PEM_read_RSA_PUBKEY(keyfile,NULL,NULL,NULL);
    RSA *rsa_pub = PEM_read_RSA_PUBKEY(keyfile2,NULL,NULL,NULL);

    if(rsa_priv==NULL)
    printf("blad rsa priv\n");

    if(rsa_pub==NULL)
    printf("blad rsa pub\n");

    bufSize=RSA_public_encrypt(64, klucz, encrypted,rsa_priv,RSA_PKCS1_PADDING);


    //bufSize=RSA_public_decrypt(bufSize, encrypted, decrypted,rsa2,RSA_PKCS1_PADDING);


   memset(&adres,0u,sizeof(struct sockaddr_storage));
   piszsyslog("Klient wystartowal",true);
glownapetla:
#ifdef DEBUG
   fprintf(stderr,"Wchodzę do głównej pętli.\n");
#endif
      stworz(&gniazdo,&adres);



/*
      nag->rozmiar=htons((int16_t)2u);
      nag->operacja=OP_HELLO;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);

      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }
*/

      /* wysyłanie certyfikatu */
      nag->rozmiar=htons((int16_t)500u);
      nag->operacja=OP_CERT;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);

      /* tu wrzuc certyfikat do dane */
      plik_temp1=fopen("klient-public.pem","r");
      if(plik_temp1==NULL)
      {
     	 piszsyslog("Nie udało się otworzyć pliku",true);
	 fprintf(stderr,"Nie udało się otworzyć pliku\n");
	 close(gniazdo);
	 goto glownapetla;
      }
      r=fread(dane,sizeof(uint8_t),500,plik_temp1);
      if(r==0)
      {
     	 piszsyslog("Nie można czytać z pliku",true);
	 fprintf(stderr,"Nie można czytać z pliku\n");
	 close(gniazdo);
	 goto glownapetla;
      }
      if(r<500)
      {
	 dane[r]='\0';
      }
      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, false);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }


      /* wysyłanie klucza */
      nag->rozmiar=htons((int16_t)128u);
      nag->operacja=OP_KEY;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);

      /* tu wrzuc klucz do dane */
    //fprintf(stderr, encrypted);

    memcpy(dane,encrypted,128u);

      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, false);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }

      nag->rozmiar=htons((int16_t)2u);
      nag->operacja=OP_SET;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);
      {
	 int16_t *dd=(int16_t *)dane;
	 *dd=htons((uint16_t)4u);
      }

      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, true);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }

      nag->rozmiar=htons((int16_t)0u);
      nag->operacja=OP_LIST;
      nag->opcje=(int8_t)0u;
      nag->ktory=htonl(ktory);
      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, true);
      if(wynik==-1)
      {
	 close(gniazdo);
	 goto glownapetla;
      }

      fflush(stdout);
      r=fwrite(dane2,sizeof(int8_t),ntohs(ack->naglowek.rozmiar)-sizeof(uint32_t),stdout); // w dane2+sizeof(uint32_t) lista katal.
      if(r!=ntohs(ack->naglowek.rozmiar)-sizeof(uint32_t))
      {
	 fprintf(stderr,"Błąd przy pisaniu na standardowe wyjście\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }
      fflush(stdout);
      wynik=fputs("\nWybierz plik: ",stdout);
      if(wynik==EOF)
      {
	 fprintf(stderr,"Nie można pisać na standardowe wyjście\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }
      if(fgets(dane,sizeof(bufor)-rozmiarNaglowka,stdin)==NULL)
      {
	 fprintf(stderr,"Nie można czytać ze standardowego wejścia\n");
	 close(gniazdo);
	 exit(EXIT_FAILURE);
      }

      if(strlen(dane)==0)
      {
	 nag->rozmiar=htons((uint16_t)0u);
	 nag->operacja=OP_QUIT;
	 nag->opcje=(uint8_t)0u;
	 nag->ktory=htonl(ktory);
	 wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, true);
	 close(gniazdo);
	 exit(EXIT_SUCCESS);
      }

      /*** Tu otwierany plik - trzeba zamknąć ***/
      dane[strlen(dane)-1]='\0';
      plik_temp=fopen(dane,"w+b");
      //piszsyslog((char) dane,true);
      if(plik_temp==NULL)
      {
     piszsyslog("Nie udało się otworzyć pliku",true);
	 fprintf(stderr,"Nie udało się otworzyć pliku\n");
	 close(gniazdo);
	 goto glownapetla;
      }

      //plik=fopen("test.tct.txt","w+b");
      plik=tmpfile();
      //piszsyslog((char) dane,true);
      if(plik==NULL)
      {
     piszsyslog("Nie udało się otworzyć pliku",true);
	 fprintf(stderr,"Nie udało się otworzyć pliku\n");
	 close(gniazdo);
	 goto glownapetla;
      }



      nag->rozmiar=htons((uint16_t)strlen(dane));
      nag->operacja=OP_GET;
      nag->opcje=(uint8_t)0u;
      nag->ktory=htonl(ktory);
      wynik=wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, true);
      if(wynik==-1)
      {
	 fclose(plik);
	 close(gniazdo);
	 goto glownapetla;
      }

      licznik=ntohl(*((uint32_t *)dane2)); //w ACK rozmiar w pakietach
#ifdef DEBUG
	 fprintf(stderr,"POCZĄTEK\n");
	 for(int zupa=0;zupa<rozmiarACK+sizeof(uint32_t);++zupa)
	 {
	    fprintf(stderr,"%02"PRIx8,bufor2[zupa]);
	 }
	 fprintf(stderr,"\nKONIEC\n");
#endif
      for(;licznik>0;--licznik)
      {
#ifdef DEBUG
	 fprintf(stderr,"Iteracja petli pobierania, licznik=%"PRIu32"\n",licznik);
#endif
	 wynik=odbierzzpotw(gniazdo,&adres,bufor,&ktory,&ktoryTam, klucz, true);
	 if(wynik==-1)
	 {
	    close(gniazdo);
	    fclose(plik);
	    goto glownapetla;
	 }

    mpz_set(klucz_aktualny,klucz_oryginalny);
    gmp_fprintf(stderr,"KLUCZ=%Qx\n",klucz_aktualny);

	for(int i=0;i<ntohs(nag->rozmiar);i++)
	{
	    dane[i]=szydefruj2(dane[i],klucz_aktualny);
	}

	 r=fwrite(dane, sizeof(int8_t), ntohs(nag->rozmiar), plik);
	 fprintf(stderr,"rozmiar=%u",ntohs(nag->rozmiar));
	 if(r!=ntohs(nag->rozmiar))
	 {
	    fprintf(stderr,"Błąd przy pisaniu na standardowe wyjście\n");
	    close(gniazdo);
	    fclose(plik);
	    goto glownapetla;
	 }
      }
      nag->rozmiar=htons((uint16_t)0u);
      nag->operacja=OP_QUIT;
      nag->opcje=(uint8_t)0u;
      nag->ktory=htonl(ktory);
      wyslijzpotw(gniazdo,&adres,bufor,bufor2,&ktory,&ktoryTam, klucz, true);
      close(gniazdo);

    fflush(plik);
        rewind(plik);
      wynik = inf(plik, plik_temp);
      if (wynik != Z_OK)
          {
              fprintf(stderr,"blad kompresji\n");
        }
      fclose(plik_temp);
      fclose(plik);


   /*
    * -1. Wybierz serwer z listy
    * -2. Nawiąż połączenie
    * -3. Jeśli się nie uda wróć do 1
    * -4. Wyślij SET z ustawieniami transmisji
    * -5. Wyślij LIST
    * -6. Pobierz listę plików
    * -7. Wyświetl listę plików
    * -8. Wczytaj od użytkownika wybór pliku (lub CTRL-D - wtedy wyślij QUIT)
    * -9. Wyślij GET nazwa_pliku
    * 10. Wyświetl postęp pobierania pliku i błędy
    * -11. Pobierz plik
    * -12. Wyświetl informację o zakończeniu pobierania
    * -13. Idź do 4
    */
   return EXIT_SUCCESS;
}
